import time
"""
配置管理模块

负责解析YAML配置文件，管理主机信息和任务配置
提供统一的配置接口供其他模块使用
"""

import yaml
import os
from typing import Dict, List, Any
from dataclasses import dataclass, field
import logging

from .command_classifier import CommandClassifier

# 获取当前模块的日志记录器
logger = logging.getLogger(__name__)


@dataclass
class Host:
    """
    主机配置数据类
    
    用于存储单个主机的连接信息和配置参数
    支持密码和密钥两种认证方式
    """
    name: str           # 主机名称，用于标识主机
    host: str           # 主机IP地址或域名
    username: str       # SSH用户名
    password: str = None        # SSH密码（可选）
    key_file: str = None        # SSH私钥文件路径（可选）
    port: int = 22              # SSH端口，默认22
    timeout: int = 30           # 连接超时时间（秒）

    def __post_init__(self):
        """初始化后处理"""
        # 验证密码或密钥文件是否存在
        if self.password is None and self.key_file is None:
            raise ValueError("Host must have either password or key_file")
        if self.password and self.key_file:
            raise ValueError("Host cannot have both password and key_file")



@dataclass
class Task:
    """
    任务配置数据类
    
    定义单个验证任务的所有配置信息
    包括命令、执行模式、依赖关系等
    """
    name: str                           # 任务名称
    command: str                        # 要执行的命令
    mode: str = "serial"                # 执行模式：serial(串行) 或 parallel(并行)
    hosts: List[str] = field(default_factory=list)     # 执行任务的主机列表
    expected: List[Dict[str, str]] = field(default_factory=list)     # 期望的输出内容（用于验证）
    unexpected: List[Dict[str, str]] = field(default_factory=list)   # 不期望的输出内容（用于验证）
    timeout: int = 30                   # 命令执行超时时间（秒）
    depends_on: List[str] = field(default_factory=list)  # 依赖的任务列表
    allow_failure: bool = False         # 是否允许失败（不影响后续任务）
    
    # 新增字段
    command_type: str = "auto"          # 命令类型：auto, normal, streaming, long_running
    streaming_config: dict = field(default_factory=dict)  # 流式命令配置
    interactive_response: List[Dict[str, str]] = field(default_factory=list)  # 自动交互响应配置
    
    def __post_init__(self):
        """初始化后处理"""
        # 如果是auto类型，自动检测命令类型
        if self.command_type == "auto":
            detected_type = CommandClassifier.classify_command(self.command)
            self.command_type = detected_type.value
        
        # 设置默认的streaming_config
        if self.command_type == "streaming" and not self.streaming_config:
            self.streaming_config = {
                'max_duration': 60,
                'line_timeout': 10,
                'show_realtime': True,
                'write_to_file': True,
                'auto_stop': True,
                'shell_inherit_task': "",
                'shell_destroy': True                            
            }


class ConfigManager:
    """
    配置文件管理器
    
    功能：
    1. 加载和解析YAML配置文件
    2. 验证配置的完整性和正确性
    3. 提供配置数据的访问接口
    4. 管理主机和任务的映射关系
    """
    
    def __init__(self, config_path: str, no_host=False):
        """
        初始化配置管理器
        
        Args:
            config_path: YAML配置文件的路径
            
        Raises:
            FileNotFoundError: 配置文件不存在
            yaml.YAMLError: YAML格式错误
        """
        self.config_path = config_path
        self.no_host = no_host

        self.config = self._load_config()          # 原始配置数据
        if not no_host:
            self.hosts = self._parse_hosts()           # 解析后的主机配置
        self.tasks = self._parse_tasks()           # 解析后的任务配置
    
    def _load_config(self) -> Dict[str, Any]:
        """
        加载YAML配置文件
        
        Returns:
            Dict[str, Any]: 解析后的配置字典
            
        Raises:
            FileNotFoundError: 配置文件不存在
            yaml.YAMLError: YAML解析错误
        """
        # 检查文件是否存在
        if not os.path.exists(self.config_path):
            raise FileNotFoundError(f"配置文件不存在: {self.config_path}")
        
        self.config_name = os.path.basename(self.config_path).split('.')[0]

        # 记录加载的时间
        self.load_time = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
        self.cmd_output_dir = "cmd_output_log/"+self.config_name+"-"+self.load_time
        # 创建目录
        # os.makedirs(self.cmd_output_dir, exist_ok=True)

        try:
            # 读取并解析YAML文件
            with open(self.config_path, 'r', encoding='utf-8') as f:
                config = yaml.safe_load(f)
            
            # 验证基本结构
            if not isinstance(config, dict):
                raise ValueError("配置文件格式无效：根节点必须是字典")
                
            logger.info(f"成功加载配置文件: {self.config_path}")
            return config
            
        except yaml.YAMLError as e:
            logger.error(f"YAML解析错误: {str(e)}")
            raise
        except Exception as e:
            logger.error(f"加载配置文件失败: {str(e)}")
            raise
    
    def _parse_hosts(self) -> Dict[str, Host]:
        """
        解析主机配置
        
        将配置文件中的hosts部分转换为Host对象字典
        
        Returns:
            Dict[str, Host]: 主机名到Host对象的映射
            
        Raises:
            ValueError: 主机配置无效
        """
        hosts = {}
        hosts_config = self.config.get('hosts', [])
        
        # 验证hosts配置格式
        if not isinstance(hosts_config, list):
            raise ValueError("hosts配置必须是列表格式")
        
        for i, host_config in enumerate(hosts_config):
            try:
                # 验证必需字段
                required_fields = ['name', 'host', 'username']
                for field in required_fields:
                    if field not in host_config:
                        raise ValueError(f"主机配置缺少必需字段: {field}")
                
                # 验证认证方式
                if not host_config.get('password') and not host_config.get('key_file'):
                    raise ValueError(f"主机 {host_config['name']} 必须配置password或key_file")
                
                # 创建Host对象
                host = Host(**host_config)
                
                # 检查主机名是否重复
                if host.name in hosts:
                    raise ValueError(f"主机名重复: {host.name}")
                
                hosts[host.name] = host
                
            except Exception as e:
                logger.error(f"解析第{i+1}个主机配置失败: {str(e)}")
                raise
        
        logger.info(f"解析到 {len(hosts)} 台主机")
        return hosts
    
    def _parse_tasks(self) -> List[Task]:
        """
        解析任务配置
        
        将配置文件中的tasks部分转换为Task对象列表
        验证任务依赖关系的正确性
        
        Returns:
            List[Task]: 任务对象列表
            
        Raises:
            ValueError: 任务配置无效或依赖关系错误
        """
        tasks = []
        tasks_config = self.config.get('tasks', [])
        
        # 验证tasks配置格式
        if not isinstance(tasks_config, list):
            raise ValueError("tasks配置必须是列表格式")
        
        task_names = set()  # 用于检查任务名重复
        
        for i, task_config in enumerate(tasks_config):
            try:
                # 验证必需字段
                required_fields = ['name', 'command']
                for field in required_fields:
                    if field not in task_config:
                        raise ValueError(f"任务配置缺少必需字段: {field}")
                
                # 如果没有指定hosts，默认在所有主机上执行
                if 'hosts' not in task_config and not self.no_host:
                    task_config['hosts'] = list(self.hosts.keys()) if self.hosts else []
                else:  
                    task_config['hosts'] = []
                
                # 验证指定的主机是否存在
                for host_name in task_config['hosts']:
                    if host_name not in self.hosts:
                        raise ValueError(f"任务 {task_config['name']} 指定的主机不存在: {host_name}")
                
                # 验证执行模式
                if task_config.get('mode', 'serial') not in ['serial', 'parallel']:
                    raise ValueError(f"任务 {task_config['name']} 的执行模式无效，必须是 serial 或 parallel")
                
                # 创建Task对象
                task = Task(**task_config)
                
                # 检查任务名是否重复
                if task.name in task_names:
                    raise ValueError(f"任务名重复: {task.name}")
                task_names.add(task.name)
                
                tasks.append(task)
                
            except Exception as e:
                logger.error(f"解析第{i+1}个任务配置失败: {str(e)}")
                raise
        
        # 验证任务依赖关系
        self._validate_task_dependencies(tasks)
        
        logger.info(f"解析到 {len(tasks)} 个任务")
        return tasks
    
    def _validate_task_dependencies(self, tasks: List[Task]):
        """
        验证任务依赖关系的正确性
        
        检查：
        1. 依赖的任务是否存在
        2. 是否存在循环依赖
        
        Args:
            tasks: 任务列表
            
        Raises:
            ValueError: 依赖关系无效
        """
        task_names = {task.name for task in tasks}
        
        # 检查依赖的任务是否存在
        for task in tasks:
            for dep in task.depends_on:
                if dep not in task_names:
                    raise ValueError(f"任务 {task.name} 依赖的任务不存在: {dep}")
        
        # 检查循环依赖（使用拓扑排序）
        def has_cycle():
            """使用DFS检测循环依赖"""
            visited = set()
            rec_stack = set()
            
            def dfs(task_name):
                if task_name in rec_stack:
                    return True  # 发现循环
                if task_name in visited:
                    return False
                
                visited.add(task_name)
                rec_stack.add(task_name)
                
                # 查找当前任务
                current_task = next((t for t in tasks if t.name == task_name), None)
                if current_task:
                    for dep in current_task.depends_on:
                        if dfs(dep):
                            return True
                
                rec_stack.remove(task_name)
                return False
            
            for task in tasks:
                if task.name not in visited:
                    if dfs(task.name):
                        return True
            return False
        
        if has_cycle():
            raise ValueError("任务依赖关系中存在循环依赖")
    
    def get_host(self, name: str) -> Host:
        """
        根据名称获取主机配置
        
        Args:
            name: 主机名称
            
        Returns:
            Host: 主机配置对象，如果不存在返回None
        """
        return self.hosts.get(name)
    
    def get_hosts_for_task(self, task: Task) -> List[Host]:
        """
        获取任务需要执行的主机列表
        
        Args:
            task: 任务对象
            
        Returns:
            List[Host]: 主机配置对象列表
        """
        # 过滤出存在的主机
        return [self.hosts[name] for name in task.hosts if name in self.hosts]
    
    def validate_config(self) -> bool:
        """
        验证配置的完整性
        
        Returns:
            bool: 配置是否有效
        """
        try:
            # 检查是否有主机配置
            if not self.hosts:
                logger.warning("没有配置任何主机")
                return False
            
            # 检查是否有任务配置
            if not self.tasks:
                logger.warning("没有配置任何任务")
                return False
            
            # 检查每个任务是否至少有一台可用主机
            for task in self.tasks:
                available_hosts = self.get_hosts_for_task(task)
                if not available_hosts:
                    logger.error(f"任务 {task.name} 没有可用的主机")
                    return False
            
            logger.info("配置验证通过")
            return True
            
        except Exception as e:
            logger.error(f"配置验证失败: {str(e)}")
            return False
    
    def get_statistics(self) -> Dict[str, Any]:
        """
        获取配置统计信息
        
        Returns:
            Dict[str, Any]: 统计信息
        """
        return {
            'total_hosts': len(self.hosts),
            'total_tasks': len(self.tasks),
            'serial_tasks': len([t for t in self.tasks if t.mode == 'serial']),
            'parallel_tasks': len([t for t in self.tasks if t.mode == 'parallel']),
            'tasks_with_dependencies': len([t for t in self.tasks if t.depends_on]),
            'tasks_allow_failure': len([t for t in self.tasks if t.allow_failure])
        }